/* 
 * Copyright (c) 2012, Fromentin Xavier, Schnell Michaël, Dervin Cyrielle, Brabant Quentin
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *      * The names of its contributors may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Fromentin Xavier, Schnell Michaël, Dervin Cyrielle OR Brabant Quentin 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package kameleon.plugin;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import kameleon.exception.KameleonException;
import kameleon.util.FileConstants;
import kameleon.util.IOObject;
import kameleon.util.IOPlugIn;
import kameleon.util.WorkSpaceManager;

/**
 * Utility class handling the plug-ins.
 * 
 * <p>Provides methods to manage (add, load and remove) different plug-ins
 * (analyzers as well as generators).
 * 
 * @author		Schnell Michaël
 * @version		1.0
 */
public class PlugInManager implements FileConstants {

	/**
	 * Default debug mode.
	 */
	public static final boolean DEFAULT_DEBUG_MODE = false ;
	
	/**
	 * Utility constant, contains only a line feed ({@code \n}).
	 */
	public static final String LINE_FEED = "\n" ; //$NON-NLS-1$
	
	/**
	 * Utility constant, contains only a tab ({@code \t}).
	 */
	public static final String TAB = "\t" ; //$NON-NLS-1$
	
	/**
	 * Replacement text for tabs a exception stack trace.
	 */
	public static final String TAB_REPLACEMENT = "  " ; //$NON-NLS-1$

	/**
	 * Used to map extensions to their latching analyzer.
	 */
	protected AnalyzerManager am ;
	
	/**
	 * List of the known analyzers of this instance.
	 */
	protected List<PlugInInfo> knownAnalyzers ;
	
	/**
	 * List of the known generators of this instance.
	 */
	protected List<PlugInInfo> knownGenerators ;
	
	/**
	 * Utility class used for reading and writing the plug-in files
	 */
	protected IOPlugIn iop ;
	
	/**
	 * Current mode for debugging.
	 */
	protected boolean debugMode ;

	/**
	 * Builds an empty instance with the given debug mode flag.
	 * 
	 * <p>The lists of plug-ins are not filled.
	 * 
	 * @param 	debugMode
	 * 			debug mode flag (use {@code true} to activate the debug mode)
	 */
	public PlugInManager(boolean debugMode) {
		super() ;
		this.knownAnalyzers = new ArrayList<PlugInInfo>() ;
		this.knownGenerators = new ArrayList<PlugInInfo>() ;
		this.am = new AnalyzerManager() ;
		this.debugMode = debugMode ;
		this.iop = new IOPlugIn(this.am, 
				ANALYSER_FOLDER, GENERATOR_FOLDER, 
				CONFIG_ANALYSER_FILE, CONFIG_GENERATOR_FILE, 
				PLUG_IN_RESOURCES_FOLDER) ;
	}// PlugInManager()
	
	/**
	 * Reads the default configuration files to fill the lists of plug-ins.
	 * 
	 * @throws 	KameleonException
	 * 			if the configuration files could not be read or interpreted successfully
	 */
	protected void initializePlugIns() throws KameleonException {
		this.readAnalyzers() ;
		this.readGenerators() ;
		this.initAM() ;
	}// initializePlugIns()

	/**
	 * Reads the default configuration file for the installed analyzers and initializes the
	 * list of known analyzers (any previous elements in the list are discarded).
	 * 
	 * @throws 	KameleonException
	 * 			if the configuration file could not be read or interpreted successfully
	 */
	private void readAnalyzers() throws KameleonException {
		this.knownAnalyzers = IOObject.readList(CONFIG_ANALYSER_FILE) ;
	}// readAnalyzers()

	/**
	 * Reads the default configuration file for the installed generators and initializes the
	 * list of known generators (any previous elements in the list are discarded).
	 * 
	 * @throws 	KameleonException
	 * 			if the configuration file could not be read or interpreted successfully
	 */
	private void readGenerators() throws KameleonException {
		this.knownGenerators = IOObject.readList(CONFIG_GENERATOR_FILE) ;
	}// readGenerators()

	/**
	 * Initializes the analyzer manager using the current list of analyzers.
	 * Any previous mappings are discarded.
	 */
	private void initAM() {
		this.am = new AnalyzerManager() ;
		for(PlugInInfo a : this.knownAnalyzers) {
			this.am.addAnalyzerInfo(a) ;
		}// for
	}// initAM()
	
	/**
	 * Display debug informations on the standard output if the debug mode is activated.
	 * 
	 * @param 	ke
	 * 			exception containing debug information
	 */
	protected void displayDebugInformation(KameleonException ke) {
		//TODO Remove hardcoded message
		if (this.debugMode) {
			System.out.printf(" Details: %s\n%s", ke.getMessage(), //$NON-NLS-1$ 
					KameleonException.getStackTraceAsString(ke)) ;
		}// if
	}// displayDebugInformation(KameleonException)

	/**
	 * Adds a new plug-in.
	 * 
	 * <p>Copies the executable code for the plug-in and update the (default)
	 * configuration files.
	 * 
	 * @param 	plugin
	 * 			file containing the new plug-in
	 * 
	 * @return	{@code PlugInInfo} about the newly added plug-in
	 * 		
	 * @throws 	KameleonException
	 * 			if the plug-in could not be added
	 */
	public PlugInInfo addPlugIn(File plugin) throws KameleonException {
		WorkSpaceManager.ensureWorkSpace() ;
		PlugInInfo info = this.iop.addPlugIn(plugin) ;
		if (info.isAnalyzer) {
			this.readAnalyzers() ;
			this.initAM() ;
		} else {
			this.readGenerators() ;
		}// if
		return info ;
	}// addPlugIn(File)

	/**
	 * Adds a new plug-in.
	 * 
	 * @param 	pluginPath
	 * 			absolute path of the file containing the new plug-in
	 * 
	 * @return	{@code PlugInInfo} about the newly added plug-in
	 * 		
	 * @throws 	KameleonException
	 * 			if the plug-in could not be added
	 * 
	 * @see		#addPlugIn(File)
	 */
	public PlugInInfo addPlugIn(String pluginPath) throws KameleonException {
		return this.addPlugIn(new File(pluginPath)) ;
	}// addPlugIn(String)

	/**
	 * Removes an analyzer plug-in.
	 *  
	 * @param 	analyzerId
	 * 			identifier of the analyzer which should be removed
	 * 
	 * @throws 	KameleonException
	 * 			if the plug-in could not be removed
	 */
	public void removeAnalyzer(String analyzerId) throws KameleonException {
		PlugInInfo analyzerInfo = this.getAnalyzer(analyzerId) ;
		if (analyzerInfo != null) {
			this.removePlugIn(this.getAnalyzer(analyzerId)) ;
		}// if
	}// removeAnalyzer(String)

	/**
	 * Removes a generator plug-in.
	 *  
	 * @param 	generatorId
	 * 			identifier of the generator which should be removed
	 * 
	 * @throws 	KameleonException
	 * 			if the plug-in could not be removed
	 */
	public void removeGenerator(String generatorId) throws KameleonException {
		this.removePlugIn(this.getGenerator(generatorId)) ;
	}// removeGenerator(String)

	/**
	 * Removes a plug-in.
	 *  
	 * @param 	info
	 * 			information about the plug-in which should be removed
	 * 
	 * @throws 	KameleonException
	 * 			if the plug-in could not be removed
	 */
	public void removePlugIn(PlugInInfo info) throws KameleonException {
		WorkSpaceManager.ensureWorkSpace() ;
		this.iop.removePlugIn(info) ;
		this.initializePlugIns() ;
	}// removePlugIn(PlugInInfo)

	/**
	 * Returns the list of known (installed) analyzer plug-ins.
	 * 
	 * @return	instance of {@code List<PlugInInfo>} which contains information
	 * 			about the installed analyzers
	 */
	public List<PlugInInfo> getKnownAnalyzers() {
		return this.knownAnalyzers;
	}// getKnownAnalysers()

	/**
	 * Returns the list of known (installed) generator plug-ins.
	 * 
	 * @return	instance of {@code List<PlugInInfo>} which contains information
	 * 			about the installed generators
	 */
	public List<PlugInInfo> getKnownGenerators() {
		return this.knownGenerators;
	}// getKnownGenerators()

	/**
	 * Returns information about the generator plug-in with the given id.
	 * 
	 * @param 	id
	 * 			identifier of the searched plug-in
	 * 
	 * @return	instance of {@code PlugInInfo} for the generator plug-in 
	 * 			with the given id, or {@code null} if no match was found
	 */
	public PlugInInfo getGenerator(String id) {
		boolean trouve = (id == null) ;
		PlugInInfo generator = null ;
		Iterator<PlugInInfo> iter = this.knownGenerators.iterator() ;
		while(!trouve && iter.hasNext()) { 
			generator = iter.next() ;
			trouve = generator.getId().equals(id) ;
		}// while
		return generator ;
	}// getGenerator(String)

	/**
	 * Returns information about the analyzer plug-in with the given id.
	 * 
	 * @param 	id
	 * 			identifier of the searched plug-in
	 * 
	 * @return	instance of {@code PlugInInfo} for the analyzer plug-in 
	 * 			with the given id, or {@code null} if no match was found
	 */
	public PlugInInfo getAnalyzer(String id) {
		boolean found = (id == null) ;
		PlugInInfo analyzer = null ;
		Iterator<PlugInInfo> iter = this.knownAnalyzers.iterator() ;
		while(!found && iter.hasNext()) { 
			analyzer = iter.next() ;
			found = analyzer.getId().equals(id) ;
		}// while
		if (found) {
			return analyzer ;
		}// if
		return null ;
	}// getAnalyzer(String)

}// class PlugInManager